/*****************************************************************************
 *  CommLayerLib - TCPServer.java
 *  Copyright (C) 2012  Matteo Piscitelli
 *  E-mail: matteo@picciux.it
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  For further info, check http://code.google.com/p/java-comm-layer/
 *
 *  SVN: $Id: TCPServer.java 41 2012-10-11 16:35:35Z picciux@gmail.com $
 *  
 *****************************************************************************/

package it.picciux.commlayer.net;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Vector;
import it.picciux.commlayer.log.Logger;
import it.picciux.commlayer.log.LoggerException;

public abstract class TCPServer implements Runnable {
	protected class Client {
		private TCPClient tcpClient;
		private OutputStream out;
		private InputStream in;
		
		public Client(TCPClient tcpClient) throws IOException {
			this.tcpClient = tcpClient;
			this.out = this.tcpClient.getOutputStream();
		}
		
		public void monitor() {
			new Thread(new Runnable() {
				public void run() {
					tcpClient.monitor();
					clientDisconnected(Client.this);
				}
			}).start();
		}
		
		public void start(ClientCode clientCode) throws IOException {
			in = tcpClient.getInputStream();
			new Thread(clientCode).start();
		}

		/**
		 * @return the tcpClient
		 */
		public TCPClient getTcpClient() {
			return tcpClient;
		}

		/**
		 * @return the out
		 */
		public OutputStream getOut() {
			return out;
		}

		/**
		 * @return the in
		 */
		public InputStream getIn() {
			return in;
		}
	}
	
	public abstract class ClientCode implements Runnable {
		protected Client client;
		
		public ClientCode(Client client) {
			this.client = client;
		}
		
		/**
		 * Descendants must implement this method with code to run on the Client, while
		 * it's connected.
		 */
		protected abstract void mainCode();
		
		public void run() {
			mainCode();
			clientDisconnected(client);
		}
	}
	
	protected boolean listening = false;
	private Vector clients = new Vector();
	private Vector clientEventListeners = new Vector();
	private String serverName = "TCPServer";

	private static Logger log;
	private static final int LOGLEVEL = Logger.OFF;
	
	public TCPServer() {
		final String logName = "it.picciux.commlayer.net.TCPServer";
		
		try {
			log = Logger.getLogger(logName, Logger.CONSOLE, null);
		} catch (LoggerException e) {
			log = Logger.getNullLogger(logName);
		}
		log.setLevel(LOGLEVEL);
	}

	public abstract void listen(int port) throws IOException;

	public synchronized boolean isListening() {
		return listening;
	}
		
	protected abstract void closeServer() throws IOException;
	
	public synchronized void stop() {
		if (! listening) return;
		
		Client[] cList = new Client[clients.size()];
		clients.copyInto(cList);
		for (int i = 0; i < cList.length; i++)
			cList[i].getTcpClient().disconnect();
		
		clients.removeAllElements();		
		
		listening = false;
		try {
			closeServer();
		} catch (IOException e) {
		}
	}
	
	public void addClientListener(INetClientEventListener listener) {
		clientEventListeners.addElement(listener);
	}
	
	public boolean removeClientListener(INetClientEventListener listener) {
		return clientEventListeners.removeElement(listener);
	}
	
	public int broadcastData(byte[] data) {
		if (! isListening()) return -1; //error
		
		int c = 0;
		
		for (int i = 0; i < clients.size(); i++) {
			OutputStream os = ((Client) clients.elementAt(i)).out;
			if (os == null) continue;
			
			try {
				os.write(data);
			} catch (IOException e) {
				continue;
			}
			
			c++;
		}
		
		return c;
	}

	
	public int broadcastData(int[] data) {
		if (! isListening()) return -1; //error
		
		int c = 0;
		
		for (int i = 0; i < clients.size(); i++) {
			OutputStream os = ((Client) clients.elementAt(i)).out;
			if (os == null) continue;
			
			try {
				for (int j = 0; j < data.length; j++) 
					os.write(data[j]);
			} catch (IOException e) {
				continue;
			}
			
			c++;
		}
		
		return c;
	}
	
	public int broadcastData(int data) {
		if (! isListening()) return -1; //error

		int c = 0;
		
		for (int i = 0; i < clients.size(); i++) {
			OutputStream os = ((Client) clients.elementAt(i)).out;
			if (os == null) continue;
			
			try {
				os.write(data);
			} catch (IOException e) {
				continue;
			}
			
			c++;
		}
		
		return c;
		
	}
	
	protected abstract TCPClient acceptClient() throws IOException;
	protected abstract boolean checkServerStatus();
	
	protected void clientDisconnected(Client client) {
		clients.removeElement(client);
		log.fine(serverName + ": client disconnected. Now they are " + clients.size());
	}
	
	public int getClientsCount() {
		return clients.size();
	}
	
	/**
	 * Override this method to provide a {@link ClientCode} implementation to run
	 * on new connected clients
	 * @param client
	 * @return a {@link ClientCode} implementation, or null if clients have to be
	 * simply monitored by server.
	 */
	protected ClientCode getClientCode(Client client) {
		return null;
	}
	
	// server thread code
	public void run() {
		while(isListening()) {
			if (! checkServerStatus()) {
				stop();
				break;
			}
			
			try {
				Client c = new Client(acceptClient());
				clients.addElement(c);
				ClientCode cc = getClientCode(c);
				if (cc != null)
					c.start(cc);
				else
					c.monitor();
				
				log.fine(serverName + ": new client connected. Now are " + clients.size());
				
			} catch (IOException e) {
			}
		}
		
	}

	/**
	 * @return the name
	 */
	public String getServerName() {
		return serverName;
	}

	/**
	 * @param name the name to set
	 */
	public void setServerName(String name) {
		this.serverName = name;
	}
}
